package api

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"reflect"
	"ssp/tech"
	"strings"
)

var solve = tech.NewSolve()

type GenericRequest struct {
	Jsonrpc string          `json:"jsonrpc"`
	Method  string          `json:"method"`
	Params  json.RawMessage `json:"params"`
	ID      int             `json:"id"`
}

type SudokuRequest struct {
	Jsonrpc string `json:"jsonrpc"`
	Method  string `json:"method"`
	Params  string `json:"params"`
	Id      int    `json:"id"`
}

type SudokuStepRequest struct {
	Jsonrpc string   `json:"jsonrpc"`
	Method  string   `json:"method"`
	Params  []string `json:"params"`
	Id      int      `json:"id"`
}

type SudokuResponse struct {
	Jsonrpc string `json:"jsonrpc"`
	Error   string `json:"error"`
	Result  string `json:"result"`
	Id      int    `json:"id"`
}

func Route(w http.ResponseWriter, r *http.Request) {
	tech.OUTPUTRESULT = true
	tech.EXHAUSTIVE = true
	tech.CHANGE = false

	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Bad request", http.StatusBadRequest)
		return
	}
	//fmt.Printf("Received request: %s\n", body)
	//resp := SolveHandler(body)
	var genericReq GenericRequest
	err = json.Unmarshal(body, &genericReq)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	var resp []byte
	switch genericReq.Method {
	case "solve":
		req := SudokuRequest{}
		err = json.Unmarshal(body, &req)

		if err != nil {
			log.Printf("Error: %v", err)
			return
		}
		resp = SolveHandler(req)
	case "make":
		req := SudokuRequest{}
		err = json.Unmarshal(body, &req)

		if err != nil {
			log.Printf("Error: %v", err)
			return
		}
		resp = MakeHandler(req)
	case "step":
		req := SudokuStepRequest{}
		err = json.Unmarshal(body, &req)

		if err != nil {
			log.Printf("Error: %v", err)
			return
		}
		resp = StepHandler(req)
	case "check":
		req := SudokuRequest{}
		err = json.Unmarshal(body, &req)

		if err != nil {
			log.Printf("Error: %v", err)
			return
		}
		resp = CheckHandler(req)
	case "strategy":
		//fmt.Println(req)
		req := SudokuRequest{}
		err = json.Unmarshal(body, &req)

		if err != nil {
			log.Printf("Error: %v", err)
			return
		}
		resp = StrategyHandler(req)
	default:
		resp = []byte("Bad request")
	}

	w.Write(resp)
}

func getSolveStrategyNames() []string {
	var strategyNames []string
	solverType := reflect.TypeOf(solve)

	for i := range solverType.NumMethod() {
		method := solverType.Method(i)
		//fmt.Println(i, method.Name, method.Type.Out(0), reflect.TypeOf(true))
		// Check method signature: func (*Grid) (bool, []*Result)
		if method.Type.NumIn() == 2 && // Receiver + params
			method.Type.NumOut() == 2 &&
			method.Type.Out(0) == reflect.TypeOf(true) &&
			method.Type.Out(1) == reflect.TypeOf([]*tech.Result{}) {
			strategyNames = append(strategyNames, method.Name)
		}
	}
	return strategyNames
}
func StrategyHandler(req SudokuRequest) []byte {
	strategyNames := getSolveStrategyNames()

	jnames, _ := json.Marshal(strategyNames)

	resp := SudokuResponse{
		Jsonrpc: "2.0",
		Error:   "",
		Result:  string(jnames),
		Id:      req.Id,
	}
	jsonResp, _ := json.Marshal(resp)
	return jsonResp
}

func CheckHandler(req SudokuRequest) []byte {
	g := &tech.Grid{}
	g.Decode(req.Params)
	g.Init()
	g.Clearup()
	count := g.GetSolvedCount()

	n := solve.Backtrack(g)

	var r string
	if n < 100 {
		r = fmt.Sprintf("The solved Number: %v\n The number of solutions: %v", count, n)
	} else {
		r = fmt.Sprintf("The solved Number: %v\n The number of solutions: 100+", count)
	}
	resp := SudokuResponse{
		Jsonrpc: "2.0",
		Error:   "",
		Result:  r,
		Id:      req.Id,
	}
	jsonResp, _ := json.Marshal(resp)
	return jsonResp
}

func SolveHandler(req SudokuRequest) []byte {
	g := &tech.Grid{}
	g.Decode(req.Params)
	g.Init()
	g.Clearup()
	solve.Backtrack(g)
	resp := SudokuResponse{
		Jsonrpc: "2.0",
		Error:   "",
		Result:  g.Encode(),
		Id:      req.Id,
	}
	jsonResp, _ := json.Marshal(resp)
	return jsonResp
}

func MakeHandler(req SudokuRequest) []byte {
	g := &tech.Grid{}
	g.Decode(req.Params)
	g.Init()
	g.Clearup()
	ok, results := solve.Make(g)
	if !ok || len(results) == 0 {
		resp := SudokuResponse{
			Jsonrpc: "2.0",
			Error:   `{"code": -32602, "message": "The puzzle can not solved."}`,
			Result:  "",
			Id:      req.Id,
		}
		jsonResp, _ := json.Marshal(resp)
		return jsonResp
	}
	rs := []string{}
	for _, result := range results {
		transNiltoSlice(result)
		r, _ := json.Marshal(result)
		rs = append(rs, string(r))
	}

	srs, _ := json.Marshal(rs)
	//fmt.Println("Result:", string(srs))
	resp := SudokuResponse{
		Jsonrpc: "2.0",
		Result:  string(srs),
		Id:      req.Id,
	}
	jsonResp, _ := json.Marshal(resp)
	return jsonResp

}

func StepHandler(req SudokuStepRequest) []byte {
	g := &tech.Grid{}
	//fmt.Println("Params:", req.Params)
	if !g.Decode(req.Params[1]) {
		resp := SudokuResponse{
			Jsonrpc: "2.0",
			Error:   `{"code": -32600, "message": "Invalid Request"}`,
			Result:  "",
			Id:      req.Id,
		}
		jsonResp, _ := json.Marshal(resp)
		return jsonResp
	}
	if g.Solved() {
		g.Init()
		var msg string
		if g.Validate() {
			msg = `{"code": -32602, "message": "The sudoku has been solved"}`
		} else {
			msg = `{"code": -32602, "message": "The sudoku is invalid"}`
		}
		resp := SudokuResponse{
			Jsonrpc: "2.0",
			Error:   msg,
			Result:  "",
			Id:      req.Id,
		}
		jsonResp, _ := json.Marshal(resp)
		return jsonResp
	}
	g.Init()
	g.Clearup()
	var used bool
	var results []*tech.Result
	methodName := strings.ReplaceAll(req.Params[0], " ", "")

	if methodName == "" {
		methodName = "Step" // Default method
	}

	solver := reflect.ValueOf(solve)
	method := solver.MethodByName(methodName)
	if method.IsValid() {
		rs := method.Call([]reflect.Value{reflect.ValueOf(g)})
		used = rs[0].Bool()
		if len(rs) > 1 {
			resultsVal := rs[1].Interface()
			if res, ok := resultsVal.([]*tech.Result); ok {
				results = res
			}
		}
	} else {
		used, results = solve.Step(g)
	}

	if !used {
		resp := SudokuResponse{
			Jsonrpc: "2.0",
			Error:   `{"code": -32600, "message": "Invalid Request"}`,
			Result:  "",
			Id:      req.Id,
		}
		jsonResp, _ := json.Marshal(resp)
		return jsonResp
	}

	rs := []string{}
	if used {
		for _, result := range results {
			transNiltoSlice(result)
			r, _ := json.Marshal(result)
			rs = append(rs, string(r))
		}
	}
	//fmt.Println("Results!!!!!!!!!!!!", rs)

	srs, _ := json.Marshal(rs)
	//fmt.Println("API Result:", string(srs))
	resp := SudokuResponse{
		Jsonrpc: "2.0",
		Result:  string(srs),
		Id:      req.Id,
	}
	jsonResp, _ := json.Marshal(resp)
	return jsonResp
}

func transNiltoSlice(result *tech.Result) {
	if result.Chosen == nil {
		result.Chosen = []string{}
	}
	if result.Removed == nil {
		result.Removed = []string{}
	}
	if result.HelpHouses == nil {
		result.HelpHouses = [][]string{}
	}
	if result.HelpCells == nil {
		result.HelpCells = [][]string{}
	}
	if result.HelpValues == nil {
		result.HelpValues = [][]string{}
	}
	if result.HelpCands == nil {
		result.HelpCands = [][]string{}
	}
	if result.HelpChains == nil {
		result.HelpChains = []string{}
	}
}
